home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / papyon / client.py < prev    next >
Text File  |  2009-10-08  |  20KB  |  500 lines

  1. # -*- coding: utf-8 -*-
  2. #
  3. # papyon - a python client library for Msn
  4. #
  5. # Copyright (C) 2005-2007 Ali Sabil <ali.sabil@gmail.com>
  6. # Copyright (C) 2006-2007 Ole Andr├⌐ Vadla Ravn├Ñs <oleavr@gmail.com>
  7. # Copyright (C) 2007 Johann Prieur <johann.prieur@gmail.com>
  8. # Copyright (C) 2008 Richard Spiers <richard.spiers@gmail.com>
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program; if not, write to the Free Software
  22. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  23.  
  24. """Client
  25.  
  26. This module contains the main class used to login into the MSN Messenger
  27. network. The following example demonstrates a simple client.
  28.  
  29.     >>> import papyon
  30.     >>>
  31.     >>> server = ('messenger.hotmail.com', 1863)
  32.     >>> account = ('papyon@hotmail.com', 'papyon is great !')
  33.     >>>
  34.     >>> client = papyon.Client(server)
  35.     >>> client.login(*account)
  36.     >>>
  37.     >>> if __name__ == "__main__":
  38.     ...     import gobject
  39.     ...     import logging
  40.     ...     logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug
  41.     ...
  42.     ...     mainloop = gobject.MainLoop()
  43.     ...     mainloop.run()
  44.  
  45. This client will try to login, but will probably fail because of the wrong
  46. password, so let's enhance this client so that it displays an error if the
  47. password was wrong, this will lead us to use the L{papyon.event} interfaces:
  48.  
  49.     >>> import papyon
  50.     >>> import papyon.event
  51.     >>>
  52.     >>> class ClientEventHandler(papyon.event.ClientEventInterface):
  53.     ...     def on_client_error(self, error_type, error):
  54.     ...         if error_type == papyon.event.ClientErrorType.AUTHENTICATION:
  55.     ...             print ""
  56.     ...             print "********************************************************"
  57.     ...             print "* You bummer ! you did input a wrong username/password *"
  58.     ...             print "********************************************************"
  59.     ...         else:
  60.     ...             print "ERROR :", error_type, " ->", error
  61.     >>>
  62.     >>>
  63.     >>> server = ('messenger.hotmail.com', 1863)
  64.     >>> account = ('papyon@hotmail.com', 'papyon is great !')
  65.     >>>
  66.     >>> client = papyon.Client(server)
  67.     >>> client_events_handler = ClientEventHandler(client)
  68.     >>>
  69.     >>> client.login(*account)
  70.     >>>
  71.     >>> if __name__ == "__main__":
  72.     ...     import gobject
  73.     ...     import logging
  74.     ...
  75.     ...     logging.basicConfig(level=logging.DEBUG) # allows us to see the protocol debug
  76.     ...
  77.     ...     mainloop = gobject.MainLoop()
  78.     ...     mainloop.run()
  79.  
  80. """
  81.  
  82.  
  83. import papyon.profile as profile
  84. import papyon.msnp as msnp
  85.  
  86. import papyon.service.SingleSignOn as SSO
  87. import papyon.service.AddressBook as AB
  88. import papyon.service.OfflineIM as OIM
  89. import papyon.service.Spaces as Spaces
  90.  
  91. from papyon.util.decorator import rw_property
  92. from papyon.transport import *
  93. from papyon.switchboard_manager import SwitchboardManager
  94. from papyon.msnp2p import P2PSessionManager
  95. from papyon.p2p import MSNObjectStore, WebcamHandler
  96. from papyon.sip import SIPConnectionManager
  97. from papyon.conversation import SwitchboardConversation, \
  98.     ExternalNetworkConversation
  99. from papyon.event import ClientState, ClientErrorType, \
  100.     AuthenticationError, ProtocolError, EventsDispatcher
  101.  
  102. import logging
  103. import uuid
  104.  
  105. __all__ = ['Client']
  106.  
  107. logger = logging.getLogger('papyon.client')
  108.  
  109. class Client(EventsDispatcher):
  110.     """This class provides way to connect to the notification server as well
  111.     as methods to manage the contact list, and the personnal settings.
  112.         @sort: __init__, login, logout, state, profile, address_book,
  113.                 msn_object_store, oim_box, spaces"""
  114.  
  115.     def __init__(self, server, proxies={}, transport_class=DirectConnection,
  116.             version=15, client_type=msnp.ClientTypes.COMPUTER):
  117.         """Initializer
  118.  
  119.             @param server: the Notification server to connect to.
  120.             @type server: tuple(host, port)
  121.  
  122.             @param proxies: proxies that we can use to connect
  123.             @type proxies: {type: string => L{gnet.proxy.ProxyInfos}}
  124.  
  125.             @param transport_class: the transport class to use for the network
  126.                     connection
  127.             @type transport_class: L{papyon.transport.BaseTransport}"""
  128.         EventsDispatcher.__init__(self)
  129.  
  130.         self.__state = ClientState.CLOSED
  131.  
  132.         self._proxies = proxies
  133.         self._transport_class = transport_class
  134.         self._client_type = client_type
  135.  
  136.         self._transport = transport_class(server, ServerType.NOTIFICATION,
  137.                 self._proxies)
  138.         self._protocol = msnp.NotificationProtocol(self, self._transport,
  139.                 self._proxies, version)
  140.  
  141.         self._switchboard_manager = SwitchboardManager(self)
  142.         self._switchboard_manager.register_handler(SwitchboardConversation)
  143.  
  144.         self._p2p_session_manager = P2PSessionManager(self)
  145.         self._webcam_handler = WebcamHandler(self)
  146.         self._p2p_session_manager.register_handler(self._webcam_handler)
  147.  
  148.         self._call_manager = SIPConnectionManager(self, self._protocol)
  149.  
  150.         self._msn_object_store = MSNObjectStore(self)
  151.         self._p2p_session_manager.register_handler(self._msn_object_store)
  152.  
  153.         self._external_conversations = {}
  154.  
  155.         self._sso = None
  156.         self._profile = None
  157.         self._address_book = None
  158.         self._oim_box = None
  159.         self._mailbox = None
  160.  
  161.         self.__die = False
  162.         self.__connect_transport_signals()
  163.         self.__connect_protocol_signals()
  164.         self.__connect_switchboard_manager_signals()
  165.         self.__connect_webcam_handler_signals()
  166.         self.__connect_call_manager_signals()
  167.  
  168.     ### public:
  169.     @property
  170.     def msn_object_store(self):
  171.         """The MSNObjectStore instance associated with this client.
  172.             @rtype: L{MSNObjectStore<papyon.p2p.MSNObjectStore>}"""
  173.         return self._msn_object_store
  174.  
  175.     @property
  176.     def webcam_handler(self):
  177.         return self._webcam_handler
  178.  
  179.     @property
  180.     def profile(self):
  181.         """The profile of the current user
  182.             @rtype: L{User<papyon.profile.Profile>}"""
  183.         return self._profile
  184.  
  185.     @property
  186.     def address_book(self):
  187.         """The address book of the current user
  188.             @rtype: L{AddressBook<papyon.service.AddressBook>}"""
  189.         return self._address_book
  190.  
  191.     @property
  192.     def call_manager(self):
  193.         """The SIP connection manager
  194.             @type: L{SIPConnectionManager<papyon.sip.SIPConnectionManager>}"""
  195.         return self._call_manager
  196.  
  197.     @property
  198.     def oim_box(self):
  199.         """The offline IM for the current user
  200.             @rtype: L{OfflineIM<papyon.service.OfflineIM>}"""
  201.         return self._oim_box
  202.  
  203.     @property
  204.     def mailbox(self):
  205.         """The mailbox of the current user
  206.             @rtype: L{<papyon.msnp.mailbox.Mailbox>}"""
  207.         return self._mailbox
  208.  
  209.     @property
  210.     def spaces(self):
  211.         """The MSN Spaces of the current user
  212.             @rtype: L{Spaces<papyon.service.Spaces>}"""
  213.         return self._spaces
  214.  
  215.     @property
  216.     def state(self):
  217.         """The state of this Client
  218.             @rtype: L{papyon.event.ClientState}"""
  219.         return self.__state
  220.  
  221.     @property
  222.     def local_ip(self):
  223.         return self._transport.sockname[0]
  224.  
  225.     @property
  226.     def protocol_version(self):
  227.         return self._protocol._protocol_version
  228.  
  229.     @property
  230.     def client_type(self):
  231.         return self._client_type
  232.  
  233.     @property
  234.     def machine_guid(self):
  235.         if not hasattr(self, '_guid'):
  236.             self._guid = str(uuid.uuid4())
  237.         return self._guid
  238.  
  239.     def login(self, account, password):
  240.         """Login to the server.
  241.  
  242.             @param account: the account to use for authentication.
  243.             @type account: utf-8 encoded string
  244.  
  245.             @param password: the password needed to authenticate to the account
  246.             @type password: utf-8 encoded string
  247.             """
  248.         if (self._state != ClientState.CLOSED):
  249.             logger.warning('login already in progress')
  250.         self.__die = False
  251.         self._state = ClientState.CONNECTING
  252.         self._profile = profile.Profile((account, password), self._protocol)
  253.         self.__connect_profile_signals()
  254.         self._mailbox = msnp.Mailbox(self._protocol)
  255.         self.__connect_mailbox_signals()
  256.         self._transport.establish_connection()
  257.  
  258.     def logout(self):
  259.         """Logout from the server."""
  260.         if self._state == ClientState.CLOSED:
  261.             logger.warning('alreay logged out')
  262.             return
  263.         self.__die = True
  264.         self._switchboard_manager.close()
  265.         if self.__state < ClientState.AUTHENTICATING:
  266.             self._transport.lose_connection()
  267.         else:
  268.             self._protocol.signoff()
  269.         self.__state = ClientState.CLOSED
  270.  
  271.     ### protected:
  272.     @rw_property
  273.     def _state():
  274.         def fget(self):
  275.             return self.__state
  276.         def fset(self, state):
  277.             self.__state = state
  278.             self._dispatch("on_client_state_changed", state)
  279.         return locals()
  280.  
  281.     def _register_external_conversation(self, conversation):
  282.         for contact in conversation.participants:
  283.             break
  284.  
  285.         if contact in self._external_conversations:
  286.             logger.warning("trying to register an external conversation twice")
  287.             return
  288.         self._external_conversations[contact] = conversation
  289.  
  290.     def _unregister_external_conversation(self, conversation):
  291.         for contact in conversation.participants:
  292.             break
  293.         del self._external_conversations[contact]
  294.  
  295.     ### private:
  296.     def __connect_profile_signals(self):
  297.         """Connect profile signals"""
  298.         def property_changed(profile, pspec):
  299.             method_name = "on_profile_%s_changed" % pspec.name.replace("-", "_")
  300.             self._dispatch(method_name)
  301.  
  302.         self.profile.connect("notify::presence", property_changed)
  303.         self.profile.connect("notify::display-name", property_changed)
  304.         self.profile.connect("notify::personal-message", property_changed)
  305.         self.profile.connect("notify::current-media", property_changed)
  306.         self.profile.connect("notify::msn-object", property_changed)
  307.  
  308.     def __connect_mailbox_signals(self):
  309.         """Connect mailbox signals"""
  310.         def new_mail_received(mailbox, mail):
  311.             self._dispatch("on_mailbox_new_mail_received", mail)
  312.  
  313.         def unread_changed(mailbox, unread_count, initial):
  314.             method_name = "on_mailbox_unread_mail_count_changed"
  315.             self._dispatch(method_name, unread_count, initial)
  316.  
  317.         self.mailbox.connect("unread-mail-count-changed", unread_changed)
  318.         self.mailbox.connect("new-mail-received", new_mail_received)
  319.  
  320.     def __connect_contact_signals(self, contact):
  321.         """Connect contact signals"""
  322.         def event(contact, *args):
  323.             event_name = args[-1]
  324.             event_args = args[:-1]
  325.             method_name = "on_contact_%s" % event_name.replace("-", "_")
  326.             self._dispatch(method_name, contact, *event_args)
  327.  
  328.         def property_changed(contact, pspec):
  329.             method_name = "on_contact_%s_changed" % pspec.name.replace("-", "_")
  330.             self._dispatch(method_name, contact)
  331.  
  332.         contact.connect("notify::memberships", property_changed)
  333.         contact.connect("notify::presence", property_changed)
  334.         contact.connect("notify::display-name", property_changed)
  335.         contact.connect("notify::personal-message", property_changed)
  336.         contact.connect("notify::current-media", property_changed)
  337.         contact.connect("notify::msn-object", property_changed)
  338.         contact.connect("notify::client-capabilities", property_changed)
  339.  
  340.         def connect_signal(name):
  341.             contact.connect(name, event, name)
  342.         connect_signal("infos-changed")
  343.  
  344.     def __connect_transport_signals(self):
  345.         """Connect transport signals"""
  346.         def connect_success(transp):
  347.             self._sso = SSO.SingleSignOn(self.profile.account,
  348.                                          self.profile.password,
  349.                                          self._proxies)
  350.             self._address_book = AB.AddressBook(self._sso, self, self._proxies)
  351.             self.__connect_addressbook_signals()
  352.             self._oim_box = OIM.OfflineMessagesBox(self._sso, self, self._proxies)
  353.             self.__connect_oim_box_signals()
  354.             self._spaces = Spaces.Spaces(self._sso, self._proxies)
  355.  
  356.             self._state = ClientState.CONNECTED
  357.  
  358.         def connect_failure(transp, reason):
  359.             self._dispatch("on_client_error", ClientErrorType.NETWORK, reason)
  360.             self._state = ClientState.CLOSED
  361.  
  362.         def disconnected(transp, reason):
  363.             if not self.__die:
  364.                 self._dispatch("on_client_error", ClientErrorType.NETWORK, reason)
  365.             self.__die = False
  366.             self._state = ClientState.CLOSED
  367.  
  368.         self._transport.connect("connection-success", connect_success)
  369.         self._transport.connect("connection-failure", connect_failure)
  370.         self._transport.connect("connection-lost", disconnected)
  371.  
  372.     def __connect_protocol_signals(self):
  373.         """Connect protocol signals"""
  374.         def state_changed(proto, param):
  375.             state = proto.state
  376.             if state == msnp.ProtocolState.AUTHENTICATING:
  377.                 self._state = ClientState.AUTHENTICATING
  378.             elif state == msnp.ProtocolState.AUTHENTICATED:
  379.                 self._state = ClientState.AUTHENTICATED
  380.             elif state == msnp.ProtocolState.SYNCHRONIZING:
  381.                 self._state = ClientState.SYNCHRONIZING
  382.             elif state == msnp.ProtocolState.SYNCHRONIZED:
  383.                 self._state = ClientState.SYNCHRONIZED
  384.             elif state == msnp.ProtocolState.OPEN:
  385.                 self._state = ClientState.OPEN
  386.                 im_contacts = self.address_book.contacts
  387.                 for contact in im_contacts:
  388.                     self.__connect_contact_signals(contact)
  389.  
  390.         def authentication_failed(proto):
  391.             self._dispatch("on_client_error", ClientErrorType.AUTHENTICATION,
  392.                            AuthenticationError.INVALID_USERNAME_OR_PASSWORD)
  393.             self.__die = True
  394.             self._transport.lose_connection()
  395.  
  396.         def disconnected_by_other(proto):
  397.             self._dispatch("on_client_error", ClientErrorType.PROTOCOL,
  398.                            ProtocolError.OTHER_CLIENT)
  399.             self.__die = True
  400.             self._transport.lose_connection()
  401.  
  402.         def server_down(proto):
  403.             self._dispatch("on_client_error", ClientErrorType.PROTOCOL,
  404.                            ProtocolError.SERVER_DOWN)
  405.             self.__die = True
  406.             self._transport.lose_connection()
  407.  
  408.         def unmanaged_message_received(proto, sender, message):
  409.             if sender in self._external_conversations:
  410.                 conversation = self._external_conversations[sender]
  411.                 conversation._on_message_received(message)
  412.             else:
  413.                 conversation = ExternalNetworkConversation(self, [sender])
  414.                 self._register_external_conversation(conversation)
  415.                 if self._dispatch("on_invite_conversation", conversation) == 0:
  416.                     logger.warning("No event handler attached for conversations")
  417.                 conversation._on_message_received(message)
  418.  
  419.         self._protocol.connect("notify::state", state_changed)
  420.         self._protocol.connect("authentication-failed", authentication_failed)
  421.         self._protocol.connect("disconnected-by-other", disconnected_by_other)
  422.         self._protocol.connect("server-down", server_down)
  423.         self._protocol.connect("unmanaged-message-received", unmanaged_message_received)
  424.  
  425.     def __connect_switchboard_manager_signals(self):
  426.         """Connect Switchboard Manager signals"""
  427.         def handler_created(switchboard_manager, handler_class, handler):
  428.             if handler_class is SwitchboardConversation:
  429.                 if self._dispatch("on_invite_conversation", handler) == 0:
  430.                     logger.warning("No event handler attached for conversations")
  431.             else:
  432.                 logger.warning("Unknown Switchboard Handler class %s" % handler_class)
  433.  
  434.         self._switchboard_manager.connect("handler-created", handler_created)
  435.  
  436.     def __connect_addressbook_signals(self):
  437.         """Connect AddressBook signals"""
  438.         def event(address_book, *args):
  439.             event_name = args[-1]
  440.             event_args = args[:-1]
  441.             if event_name == "contact-added":
  442.                 self.__connect_contact_signals(event_args[0])
  443.                 # Call the old event name to be backward compatible
  444.                 self._dispatch("on_addressbook_messenger_contact_added", *event_args)
  445.             method_name = "on_addressbook_%s" % event_name.replace("-", "_")
  446.             self._dispatch(method_name, *event_args)
  447.         def error(address_book, error_code):
  448.             self._dispatch("on_client_error", ClientErrorType.ADDRESSBOOK, error_code)
  449.             self.__die = True
  450.             self._transport.lose_connection()
  451.  
  452.         self.address_book.connect('error', error)
  453.  
  454.         def connect_signal(name):
  455.             self.address_book.connect(name, event, name)
  456.  
  457.         connect_signal("contact-added")
  458.         connect_signal("contact-deleted")
  459.         connect_signal("contact-blocked")
  460.         connect_signal("contact-unblocked")
  461.         connect_signal("group-added")
  462.         connect_signal("group-deleted")
  463.         connect_signal("group-renamed")
  464.         connect_signal("group-contact-added")
  465.         connect_signal("group-contact-deleted")
  466.  
  467.     def __connect_oim_box_signals(self):
  468.         """Connect Offline IM signals"""
  469.         def event(oim_box, *args):
  470.             method_name = "on_oim_%s" % args[-1].replace("-", "_")
  471.             self._dispatch(method_name, *args[:-1])
  472.         def state_changed(oim_box, pspec):
  473.             self._dispatch("on_oim_state_changed", oim_box.state)
  474.         def error(oim_box, error_code):
  475.             self._dispatch("on_client_error", ClientErrorType.OFFLINE_MESSAGES, error_code)
  476.  
  477.         self.oim_box.connect("notify::state", state_changed)
  478.         self.oim_box.connect('error', error)
  479.  
  480.         def connect_signal(name):
  481.             self.oim_box.connect(name, event, name)
  482.         connect_signal("messages-received")
  483.         connect_signal("messages-fetched")
  484.         connect_signal("message-sent")
  485.         connect_signal("messages-deleted")
  486.  
  487.     def __connect_webcam_handler_signals(self):
  488.         """Connect Webcam Handler signals"""
  489.         def session_created(webcam_handler, session, producer):
  490.             self._dispatch("on_invite_webcam", session, producer)
  491.  
  492.         self._webcam_handler.connect("session-created", session_created)
  493.  
  494.     def __connect_call_manager_signals(self):
  495.         """Connect SIP Call Manager signals"""
  496.         def invite_received(call_manager, call):
  497.             self._dispatch("on_invite_conference", call)
  498.  
  499.         self._call_manager.connect("invite-received", invite_received)
  500.